# uncompyle6 version 3.2.3
# Python bytecode 3.6 (3379)
# Decompiled from: Python 3.6.8 |Anaconda custom (64-bit)| (default, Feb 21 2019, 18:30:04) [MSC v.1916 64 bit (AMD64)]
# Embedded file name: site-packages\PyInstaller\archive\writers.py
"""
Utilities to create data structures for embedding Python modules and additional
files into the executable.
"""
import os, sys, struct
from types import CodeType
import marshal, zlib
from PyInstaller.building.utils import get_code_object, strip_paths_in_code
from .readers import PYZ_TYPE_MODULE, PYZ_TYPE_PKG, PYZ_TYPE_DATA
from ..compat import BYTECODE_MAGIC, is_py2


class ArchiveWriter(object):
    """
    A base class for a repository of python code objects.
    The extract method is used by imputil.ArchiveImporter
    to get code objects by name (fully qualified name), so
    an enduser "import a.b" would become
      extract('a.__init__')
      extract('a.b')
    """

    MAGIC = b"PYL\x00"
    HDRLEN = 12
    TOCPOS = 8

    def __init__(self, archive_path, logical_toc):
        """
        Create an archive file of name 'archive_path'.
        logical_toc is a 'logical TOC' - a list of (name, path, ...)
        where name is the internal name, eg 'a'
        and path is a file to get the object from, eg './a.pyc'.
        """
        self.start = 0
        self._start_add_entries(archive_path)
        self._add_from_table_of_contents(logical_toc)
        self._finalize()

    def _start_add_entries(self, archive_path):
        """
        Open an empty archive for addition of entries.
        """
        self.lib = open(archive_path, "wb")
        if self.HDRLEN:
            self.lib.write(b"\x00" * self.HDRLEN)
        self.toc = []

    def _add_from_table_of_contents(self, toc):
        """
        Add entries from a logical TOC (without absolute positioning info).
        An entry is an entry in a logical TOC is a tuple,
          entry[0] is name (under which it will be saved).
          entry[1] is fullpathname of the file.
          entry[2] is a flag for it's storage format (True or 1 if compressed)
          entry[3] is the entry's type code.
        """
        for toc_entry in toc:
            self.add(toc_entry)

    def _finalize(self):
        """
        Finalize an archive which has been opened using _start_add_entries(),
        writing any needed padding and the table of contents.
        """
        toc_pos = self.lib.tell()
        self.save_trailer(toc_pos)
        if self.HDRLEN:
            self.update_headers(toc_pos)
        self.lib.close()

    def add(self, entry):
        """
        Override this to influence the mechanics of the Archive.
        Assumes entry is a seq beginning with (nm, pth, ...) where
        nm is the key by which we'll be asked for the object.
        pth is the name of where we find the object. Overrides of
        get_obj_from can make use of further elements in entry.
        """
        nm = entry[0]
        pth = entry[1]
        pynm, ext = os.path.splitext(os.path.basename(pth))
        ispkg = pynm == "__init__"
        if not ext in (".pyc", ".pyo"):
            raise AssertionError
        self.toc.append((nm, (ispkg, self.lib.tell())))
        f = open(entry[1], "rb")
        f.seek(8)
        self.lib.write(f.read())

    def save_trailer(self, tocpos):
        """
        Default - toc is a dict
        Gets marshaled to self.lib
        """
        try:
            self.lib.write(marshal.dumps(self.toc))
        except ValueError as exception:
            if str(exception) == "unmarshallable object":
                MARSHALLABLE_TYPES = set(
                    (
                        bool,
                        int,
                        float,
                        complex,
                        str,
                        bytes,
                        bytearray,
                        tuple,
                        list,
                        set,
                        frozenset,
                        dict,
                        CodeType,
                    )
                )
                if sys.version_info[0] == 2:
                    MARSHALLABLE_TYPES.add(long)
                for module_name, module_tuple in self.toc.items():
                    if type(module_name) not in MARSHALLABLE_TYPES:
                        print(
                            'Module name "%s" (%s) unmarshallable.'
                            % (module_name, type(module_name))
                        )
                    if type(module_tuple) not in MARSHALLABLE_TYPES:
                        print(
                            'Module "%s" tuple "%s" (%s) unmarshallable.'
                            % (module_name, module_tuple, type(module_tuple))
                        )
                    elif type(module_tuple) == tuple:
                        for i in range(len(module_tuple)):
                            if type(module_tuple[i]) not in MARSHALLABLE_TYPES:
                                print(
                                    'Module "%s" tuple index %s item "%s" (%s) unmarshallable.'
                                    % (
                                        module_name,
                                        i,
                                        module_tuple[i],
                                        type(module_tuple[i]),
                                    )
                                )

            raise

    def update_headers(self, tocpos):
        """
        Default - MAGIC + Python's magic + tocpos
        """
        self.lib.seek(self.start)
        self.lib.write(self.MAGIC)
        self.lib.write(BYTECODE_MAGIC)
        self.lib.write(struct.pack("!i", tocpos))


class ZlibArchiveWriter(ArchiveWriter):
    """
    ZlibArchive - an archive with compressed entries. Archive is read
    from the executable created by PyInstaller.
    
    This archive is used for bundling python modules inside the executable.
    
    NOTE: The whole ZlibArchive (PYZ) is compressed so it is not necessary
          to compress single modules with zlib.
    """

    MAGIC = b"PYZ\x00"
    TOCPOS = 8
    HDRLEN = ArchiveWriter.HDRLEN + 5
    COMPRESSION_LEVEL = 6

    def __init__(self, archive_path, logical_toc, code_dict=None, cipher=None):
        """
        code_dict      dict containing module code objects from ModuleGraph.
        """
        self.code_dict = code_dict or {}
        self.cipher = cipher or None
        super(ZlibArchiveWriter, self).__init__(archive_path, logical_toc)

    def add(self, entry):
        name, path, typ = entry
        if typ == "PYMODULE":
            typ = PYZ_TYPE_MODULE
            if path in ("-", None):
                typ = PYZ_TYPE_PKG
            else:
                base, ext = os.path.splitext(os.path.basename(path))
                if base == "__init__":
                    typ = PYZ_TYPE_PKG
            data = marshal.dumps(self.code_dict[name])
        else:
            typ = PYZ_TYPE_DATA
            with open(path, "rb") as (fh):
                data = fh.read()
        obj = zlib.compress(data, self.COMPRESSION_LEVEL)
        if self.cipher:
            obj = self.cipher.encrypt(obj)
        self.toc.append((name, (typ, self.lib.tell(), len(obj))))
        self.lib.write(obj)

    def update_headers(self, tocpos):
        """
        add level
        """
        ArchiveWriter.update_headers(self, tocpos)
        self.lib.write(struct.pack("!B", self.cipher is not None))


class CTOC(object):
    """
    A class encapsulating the table of contents of a CArchive.
    
    When written to disk, it is easily read from C.
    """

    ENTRYSTRUCT = "!iiiiBB"
    ENTRYLEN = struct.calcsize(ENTRYSTRUCT)

    def __init__(self):
        self.data = []

    def tobinary(self):
        """
        Return self as a binary string.
        """
        rslt = []
        for dpos, dlen, ulen, flag, typcd, nm in self.data:
            if is_py2:
                if isinstance(nm, str):
                    nm = nm.decode(sys.getfilesystemencoding())
            nm = nm.encode("utf-8")
            nmlen = len(nm) + 1
            toclen = nmlen + self.ENTRYLEN
            if toclen % 16 == 0:
                pad = b"\x00"
            else:
                padlen = 16 - toclen % 16
                pad = b"\x00" * padlen
                nmlen = nmlen + padlen
            rslt.append(
                struct.pack(
                    self.ENTRYSTRUCT + "%is" % nmlen,
                    nmlen + self.ENTRYLEN,
                    dpos,
                    dlen,
                    ulen,
                    flag,
                    ord(typcd),
                    nm + pad,
                )
            )

        return (b"").join(rslt)

    def add(self, dpos, dlen, ulen, flag, typcd, nm):
        """
        Add an entry to the table of contents.
        
        DPOS is data position.
        DLEN is data length.
        ULEN is the uncompressed data len.
        FLAG says if the data is compressed.
        TYPCD is the "type" of the entry (used by the C code)
        NM is the entry's name.
        
        This function is used only while creating an executable.
        """
        nm = os.path.normpath(nm)
        self.data.append((dpos, dlen, ulen, flag, typcd, nm))


class CArchiveWriter(ArchiveWriter):
    """
    An Archive subclass that can hold arbitrary data.
    
    This class encapsulates all files that are bundled within an executable.
    It can contain ZlibArchive (Python .pyc files), dlls, Python C extensions
    and all other data files that are bundled in --onefile mode.
    
    Easily handled from C or from Python.
    """

    MAGIC = b"MEI\x0c\x0b\n\x0b\x0e"
    HDRLEN = 0
    LEVEL = 9
    _cookie_format = "!8siiii64s"
    _cookie_size = struct.calcsize(_cookie_format)

    def __init__(self, archive_path, logical_toc, pylib_name):
        """
        Constructor.
        
        archive_path path name of file (create empty CArchive if path is None).
        start        is the seekposition within PATH.
        len          is the length of the CArchive (if 0, then read till EOF).
        pylib_name   name of Python DLL which bootloader will use.
        """
        self._pylib_name = pylib_name
        super(CArchiveWriter, self).__init__(archive_path, logical_toc)

    def _start_add_entries(self, path):
        """
        Open an empty archive for addition of entries.
        """
        super(CArchiveWriter, self)._start_add_entries(path)
        self.toc = CTOC()

    def add(self, entry):
        """
        Add an ENTRY to the CArchive.
        
        ENTRY must have:
          entry[0] is name (under which it will be saved).
          entry[1] is fullpathname of the file.
          entry[2] is a flag for it's storage format (0==uncompressed,
          1==compressed)
          entry[3] is the entry's type code.
          Version 5:
            If the type code is 'o':
              entry[0] is the runtime option
              eg: v  (meaning verbose imports)
                  u  (menaing unbuffered)
                  W arg (warning option arg)
                  s  (meaning do site.py processing.
        """
        nm, pathnm, flag, typcd = entry[:4]
        code_data = None
        fh = None
        try:
            if typcd in ("o", "d"):
                ulen = 0
                flag = 0
            else:
                if typcd == "s":
                    code = get_code_object(nm, pathnm)
                    code = strip_paths_in_code(code)
                    code_data = marshal.dumps(code)
                    ulen = len(code_data)
                else:
                    fh = open(pathnm, "rb")
                    ulen = os.fstat(fh.fileno()).st_size
        except IOError:
            print("Cannot find ('%s', '%s', %s, '%s')" % (nm, pathnm, flag, typcd))
            raise

        where = self.lib.tell()
        if not flag in range(3):
            raise AssertionError
        if not fh:
            if not code_data:
                pass
            if flag == 1:
                comprobj = zlib.compressobj(self.LEVEL)
                if code_data is not None:
                    self.lib.write(comprobj.compress(code_data))
                else:
                    if not fh:
                        raise AssertionError
                    while True:
                        buf = fh.read(16384)
                        if not buf:
                            break
                        self.lib.write(comprobj.compress(buf))

                self.lib.write(comprobj.flush())
            else:
                if code_data is not None:
                    self.lib.write(code_data)
                else:
                    if not fh:
                        raise AssertionError
                    while True:
                        buf = fh.read(16384)
                        if not buf:
                            break
                        self.lib.write(buf)

                dlen = self.lib.tell() - where
                if typcd == "m":
                    if pathnm.find(".__init__.py") > -1:
                        typcd = "M"
                self.toc.add(where, dlen, ulen, flag, typcd, nm)

    def save_trailer(self, tocpos):
        """
        Save the table of contents and the cookie for the bootlader to
        disk.
        
        CArchives can be opened from the end - the cookie points
        back to the start.
        """
        tocstr = self.toc.tobinary()
        self.lib.write(tocstr)
        toclen = len(tocstr)
        total_len = tocpos + toclen + self._cookie_size
        pyvers = sys.version_info[0] * 10 + sys.version_info[1]
        cookie = struct.pack(
            self._cookie_format,
            self.MAGIC,
            total_len,
            tocpos,
            toclen,
            pyvers,
            self._pylib_name.encode("ascii"),
        )
        self.lib.write(cookie)
